package com.chenwc.util.fileutil.jarfile;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 压缩文件工具
 *
 * @author chenwc
 * @package com.chenwc.util.fileutil.JarFileUtil
 * @project wso2-sinicization
 * @date 2024/5/25
 */
public class Compressor {
    private static final Logger log = LoggerFactory.getLogger(Compressor.class);

    //缓存
    private static final int BUFFER = 8192;

    //新打包后的压缩包路径+文件名
    private final File fileName;

    private int fileCount = 0;
    private int directoryCount = 0;

    //用于压缩包删除前缀目录
    private final String originalUrl;

    /**
     * 构造函数
     *
     * @param pathName 新打包后的压缩包路径
     */
    public Compressor(String pathName) {
        log.info("pathName: {}", pathName);
        fileName = new File(pathName);
        fileCount = 0;
        directoryCount = 0;
        originalUrl = fileName.getName().replace(".jar", "");
        try {
            if (!fileName.exists()) {
                FileUtils.touch(fileName);
            }
        } catch (IOException e) {
            log.error("Error occurred: ", e);
        }
    }

    /**
     * 压缩文件列表
     *
     * @param filesList 需要压缩的文件路径列表
     */
    public void compress(List<File> filesList) {
        ZipOutputStream out = null;
        BufferedInputStream bis = null;
        FileInputStream fis = null;
        FileOutputStream fileOutputStream = null;
        CheckedOutputStream cos = null;
        try {
            fileOutputStream = new FileOutputStream(fileName);
            cos = new CheckedOutputStream(fileOutputStream, new CRC32());
            out = new ZipOutputStream(cos);
            for (File file : filesList) {
                if (file.isDirectory()) {
                    directoryCount++;
                } else {
                    fileCount++;
                }
                fis = new FileInputStream(file);
                bis = new BufferedInputStream(fis);
                String filePath = file.getAbsolutePath().substring(file.getAbsolutePath().indexOf(originalUrl) + originalUrl.length() + 1);
                log.debug("压缩文件：" + filePath);
                ZipEntry entry = new ZipEntry(filePath);
                out.putNextEntry(entry);
                int count;
                byte[] data = new byte[BUFFER];
                while ((count = bis.read(data, 0, BUFFER)) != -1) {
                    out.write(data, 0, count);
                }
            }
            log.info("压缩完成，共压缩 {} 个文件， {} 个目录", fileCount, Math.max((directoryCount - 1), 0));
        } catch (Exception e) {
            log.error("Error occurred: ", e);
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (bis != null) {
                    bis.close();
                }
                if (fis != null) {
                    fis.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
                if (cos != null) {
                    cos.close();
                }
            } catch (Exception e) {
                log.error("Error occurred: ", e);
            }
        }
    }

    /**
     * 压缩单个文件
     *
     * @param srcPathName 需要压缩的文件路径
     */
    public void compress(String srcPathName) {
        File file = new File(srcPathName);
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(fileName);
            CheckedOutputStream cos = new CheckedOutputStream(fileOutputStream, new CRC32());
            ZipOutputStream out = new ZipOutputStream(cos);
            String basedir = "";
            compress(file, out, basedir);
            out.close();
            log.info("压缩完成，共压缩 {} 个文件， {} 个目录", fileCount, Math.max((directoryCount - 1), 0));
        } catch (Exception e) {
            log.error("Error occurred: ", e);
        }
    }

    /**
     * 压缩文件和目录
     *
     * @param file    需要添加进压缩包的文件
     * @param out     zip文件输出流
     * @param basedir 用于压缩包删除前缀目录
     */
    private void compress(File file, ZipOutputStream out, String basedir) {
        /* 判断是目录还是文件 */
        if (file.isDirectory()) {
            directoryCount++;
            this.compressDirectory(file, out, basedir);
        } else {
            fileCount++;
            this.compressFile(file, out, basedir);
        }
    }

    /**
     * 压缩目录
     *
     * @param dir     需要添加进压缩包的目录
     * @param out     zip文件输出流
     * @param basedir 用于压缩包删除前缀目录
     */
    private void compressDirectory(File dir, ZipOutputStream out, String basedir) {
        if (!dir.exists()) {
            log.info("目录不存在");
            return;
        }
        File[] files = dir.listFiles();
        if (null == files) {
            throw new RuntimeException(dir.getAbsolutePath() + " 目录下文件列表为空！");
        }
        for (File file : files) {
            /* 递归 */
            compress(file, out, basedir + dir.getName() + "/");
        }
    }

    /**
     * 压缩文件
     *
     * @param file    需要添加进压缩包的文件
     * @param out     zip文件输出流
     * @param basedir 用于压缩包删除前缀目录
     */
    private void compressFile(File file, ZipOutputStream out, String basedir) {
        if (!file.exists()) {
            log.info(file.getAbsolutePath() + " 文件不存在！");
            return;
        }
        try {
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            String filePath = (basedir + file.getName()).replaceAll(getOriginalUrl() + "/", "");
            log.debug("压缩文件：" + filePath);
            ZipEntry entry = new ZipEntry(filePath);
            out.putNextEntry(entry);
            int count;
            byte[] data = new byte[BUFFER];
            while ((count = bis.read(data, 0, BUFFER)) != -1) {
                out.write(data, 0, count);
            }
            bis.close();
        } catch (Exception e) {
            log.error("Error occurred: ", e);
        }
    }

    /**
     * 获取用于压缩包删除前缀目录
     *
     * @return 用于压缩包删除前缀目录
     */
    public String getOriginalUrl() {
        return originalUrl;
    }

}
